import 'dart:math' as math;

import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/collection_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/models/map/map_event.model.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/providers/timeline.provider.dart';
import 'package:immich_mobile/utils/color_filter_generator.dart';
import 'package:immich_mobile/utils/throttle.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/widgets/common/drag_sheet.dart';
import 'package:logging/logging.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';

class MapAssetGrid extends HookConsumerWidget {
  final Stream<MapEvent> mapEventStream;
  final Function(String)? onGridAssetChanged;
  final Function(String)? onZoomToAsset;
  final Function(bool, Set<Asset>)? onAssetsSelected;
  final ValueNotifier<Set<Asset>> selectedAssets;
  final ScrollController controller;

  const MapAssetGrid({
    required this.mapEventStream,
    this.onGridAssetChanged,
    this.onZoomToAsset,
    this.onAssetsSelected,
    required this.selectedAssets,
    required this.controller,
    super.key,
  });

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final log = Logger("MapAssetGrid");
    final assetsInBounds = useState<List<Asset>>([]);
    final cachedRenderList = useRef<RenderList?>(null);
    final lastRenderElementIndex = useRef<int?>(null);
    final assetInSheet = useValueNotifier<String?>(null);
    final gridScrollThrottler = useThrottler(interval: const Duration(milliseconds: 300));

    // Add a cache for assets we've already loaded
    final assetCache = useRef<Map<String, Asset>>({});

    void handleMapEvents(MapEvent event) async {
      if (event is MapAssetsInBoundsUpdated) {
        final assetIds = event.assetRemoteIds;
        final missingIds = <String>[];
        final currentAssets = <Asset>[];

        for (final id in assetIds) {
          final asset = assetCache.value[id];
          if (asset != null) {
            currentAssets.add(asset);
          } else {
            missingIds.add(id);
          }
        }

        // Only fetch missing assets
        if (missingIds.isNotEmpty) {
          final newAssets = await ref.read(dbProvider).assets.getAllByRemoteId(missingIds);

          // Add new assets to cache and current list
          for (final asset in newAssets) {
            if (asset.remoteId != null) {
              assetCache.value[asset.remoteId!] = asset;
              currentAssets.add(asset);
            }
          }
        }

        assetsInBounds.value = currentAssets;
        return;
      }
    }

    useOnStreamChange<MapEvent>(mapEventStream, onData: handleMapEvents);

    // Hard-restrict to 4 assets / row in portrait mode
    const assetsPerRow = 4;

    void handleVisibleItems(Iterable<ItemPosition> positions) {
      final orderedPos = positions.sortedByField((p) => p.index);
      // Index of row where the items are mostly visible
      const partialOffset = 0.20;
      final item = orderedPos.firstWhereOrNull((p) => p.itemTrailingEdge > partialOffset);

      // Guard no elements, reset state
      // Also fail fast when the sheet is just opened and the user is yet to scroll (i.e leading = 0)
      if (item == null || item.itemLeadingEdge == 0) {
        lastRenderElementIndex.value = null;
        return;
      }

      final renderElement = cachedRenderList.value?.elements.elementAtOrNull(item.index);
      // Guard no render list or render element
      if (renderElement == null) {
        return;
      }
      // Reset index
      lastRenderElementIndex.value == item.index;

      //  <RenderElement:offset:0>
      //  | 1 | 2 | 3 | 4 | 5 | 6 |
      //  <RenderElement:offset:6>
      //  | 7 | 8 | 9 |
      //  <RenderElement:offset:9>
      //  | 10 |

      // Skip through the assets from the previous row
      final rowOffset = renderElement.offset;
      // Column offset = (total trailingEdge - trailingEdge crossed) / offset for each asset
      final totalOffset = item.itemTrailingEdge - item.itemLeadingEdge;
      final edgeOffset =
          (totalOffset - partialOffset) /
          // Round the total count to the next multiple of [assetsPerRow]
          ((renderElement.totalCount / assetsPerRow) * assetsPerRow).floor();

      // trailing should never be above the totalOffset
      final columnOffset = (totalOffset - math.min(item.itemTrailingEdge, totalOffset)) ~/ edgeOffset;
      final assetOffset = rowOffset + columnOffset;
      final selectedAsset = cachedRenderList.value?.allAssets?.elementAtOrNull(assetOffset)?.remoteId;

      if (selectedAsset != null) {
        onGridAssetChanged?.call(selectedAsset);
        assetInSheet.value = selectedAsset;
      }
    }

    return Card(
      margin: EdgeInsets.zero,
      child: Stack(
        children: [
          /// The Align and FractionallySizedBox are to prevent the Asset Grid from going behind the
          /// _MapSheetDragRegion and thereby displaying content behind the top right and top left curves
          Align(
            alignment: Alignment.bottomCenter,
            child: FractionallySizedBox(
              // Place it just below the drag handle
              heightFactor: 0.87,
              child: assetsInBounds.value.isNotEmpty
                  ? ref
                        .watch(assetsTimelineProvider(assetsInBounds.value))
                        .when(
                          data: (renderList) {
                            // Cache render list here to use it back during visibleItemsListener
                            cachedRenderList.value = renderList;
                            return ValueListenableBuilder(
                              valueListenable: selectedAssets,
                              builder: (_, value, __) => ImmichAssetGrid(
                                shrinkWrap: true,
                                renderList: renderList,
                                showDragScroll: false,
                                assetsPerRow: assetsPerRow,
                                showMultiSelectIndicator: false,
                                selectionActive: value.isNotEmpty,
                                listener: onAssetsSelected,
                                visibleItemsListener: (pos) => gridScrollThrottler.run(() => handleVisibleItems(pos)),
                              ),
                            );
                          },
                          error: (error, stackTrace) {
                            log.warning("Cannot get assets in the current map bounds", error, stackTrace);
                            return const SizedBox.shrink();
                          },
                          loading: () => const SizedBox.shrink(),
                        )
                  : const _MapNoAssetsInSheet(),
            ),
          ),
          _MapSheetDragRegion(
            controller: controller,
            assetsInBoundCount: assetsInBounds.value.length,
            assetInSheet: assetInSheet,
            onZoomToAsset: onZoomToAsset,
          ),
        ],
      ),
    );
  }
}

class _MapNoAssetsInSheet extends StatelessWidget {
  const _MapNoAssetsInSheet();

  @override
  Widget build(BuildContext context) {
    const image = Image(height: 150, width: 150, image: AssetImage('assets/lighthouse.png'));

    return Center(
      child: ListView(
        shrinkWrap: true,
        children: [
          context.isDarkTheme
              ? const InvertionFilter(
                  child: SaturationFilter(saturation: -1, child: BrightnessFilter(brightness: -5, child: image)),
                )
              : image,
          const SizedBox(height: 20),
          Center(
            child: Text("map_zoom_to_see_photos".tr(), style: context.textTheme.displayLarge?.copyWith(fontSize: 18)),
          ),
        ],
      ),
    );
  }
}

class _MapSheetDragRegion extends StatelessWidget {
  final ScrollController controller;
  final int assetsInBoundCount;
  final ValueNotifier<String?> assetInSheet;
  final Function(String)? onZoomToAsset;

  const _MapSheetDragRegion({
    required this.controller,
    required this.assetsInBoundCount,
    required this.assetInSheet,
    this.onZoomToAsset,
  });

  @override
  Widget build(BuildContext context) {
    final assetsInBoundsText = "map_assets_in_bounds".t(context: context, args: {'count': assetsInBoundCount});

    return SingleChildScrollView(
      controller: controller,
      physics: const ClampingScrollPhysics(),
      child: Card(
        margin: EdgeInsets.zero,
        shape: context.isMobile
            ? const RoundedRectangleBorder(
                borderRadius: BorderRadius.only(topRight: Radius.circular(20), topLeft: Radius.circular(20)),
              )
            : const BeveledRectangleBorder(),
        elevation: 0.0,
        child: Stack(
          children: [
            Column(
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const SizedBox(height: 15),
                const CustomDraggingHandle(),
                const SizedBox(height: 15),
                Center(
                  child: Text(
                    assetsInBoundsText,
                    style: TextStyle(
                      fontSize: 20,
                      color: context.textTheme.displayLarge?.color?.withValues(alpha: 0.75),
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                ),
                const SizedBox(height: 8),
              ],
            ),
            ValueListenableBuilder(
              valueListenable: assetInSheet,
              builder: (_, value, __) => Visibility(
                visible: value != null,
                child: Positioned(
                  right: 18,
                  top: 24,
                  child: IconButton(
                    icon: Icon(Icons.map_outlined, color: context.textTheme.displayLarge?.color),
                    iconSize: 24,
                    tooltip: 'zoom_to_bounds'.tr(),
                    onPressed: () => onZoomToAsset?.call(value!),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
